/** @file matrix_dataset.cc
 *
 *  @author Dongryeol Lee (drselee@gmail.com)
 */

#include <fstream>
#include <functional>
#include <ios>

#include "core/data/csv_parser.h"
#include "core/data/matrix_dataset.h"
#include "core/util/random.h"

namespace core {
namespace data {

MatrixDataset::MatrixDataset() {
  average_num_points_ = 0.0;
}

MatrixDataset::~MatrixDataset() {
  for(int i = 0; i < static_cast<int>(col_collections_.size()); i++) {
    delete col_collections_[i];
    delete raw_point_collections_[i];
    delete raw_target_collections_[i];
  }
}

double MatrixDataset::average_num_points() const {
  return average_num_points_;
}

arma::vec &MatrixDataset::get_instance(int point_id) {
  return * (col_collections_[ point_id]);
}

const arma::vec &MatrixDataset::get_instance(int point_id) const {
  return * (col_collections_[ point_id]);
}

core::data::alias_vec &MatrixDataset::get_instance(
  int point_id, core::data::alias_vec **targets_out) {
  * targets_out =  raw_target_collections_[ point_id ] ;
  return * (raw_point_collections_[ point_id]);
}

const core::data::alias_vec &MatrixDataset::get_instance(
  int point_id,
  const core::data::alias_vec **targets_out) const {
  *targets_out = raw_target_collections_[point_id];
  return * (raw_point_collections_[ point_id]);
}

void MatrixDataset::Init(int num_attributes_in, int num_points_in) {

  // Set up in a default manner.
  this->SetupDefaultAttributeList_(num_attributes_in);

  // Initialize and setup pointers.
  matrix_.zeros(num_attributes_in, num_points_in);
  col_collections_.resize(num_points_in);
  raw_point_collections_.resize(num_points_in);
  raw_target_collections_.resize(num_points_in);
  double *memptr = matrix_.memptr();
  for(int i = 0; i < num_points_in; i++, memptr += num_attributes_in) {
    col_collections_[i] = new arma::vec(memptr, num_attributes_in, false);
    raw_point_collections_[i] =
      new core::data::alias_vec(
      * col_collections_[i], raw_point_attribute_indices_);
    raw_target_collections_[i] =
      new core::data::alias_vec(
      * col_collections_[i], raw_target_attribute_indices_);
  }

  // Set the avearge number of points.
  average_num_points_ = num_points_in;
}

void MatrixDataset::Init(
  boost::mpi::communicator &world,
  const std::string &file_name_in, bool from_single_file)  {

  if(! from_single_file) {

    // Treat the file name as a prefix.
    std::stringstream file_name_sstr;
    file_name_sstr << file_name_in << world.rank();
    std::string file_name(file_name_sstr.str());
    core::data::CSVParser::Parse(
      file_name, this, 0, std::numeric_limits<int>::max());
  }
  else {

    // The received partition.
    std::pair< int, int > received_partition;

    // The master partitions and sends each partition to each process.
    if(world.rank() == 0) {
      std::ifstream is(file_name_in.c_str(), std::ios_base::binary);
      is.seekg(0, std::ios::end);
      int length = is.tellg();
      is.clear();
      is.seekg(0, std::ios::beg);

      // Partition of the text file.
      std::vector < std::pair<int, int> > partition_list;
      FindPartition_(length, world.size(), is, true, & partition_list);
      boost::mpi::scatter(world, partition_list, received_partition, 0);
    }
    else {
      boost::mpi::scatter(world, received_partition, 0);
    }

    // Perform parallel parsing.
    core::data::CSVParser::Parse(
      file_name_in, this, received_partition.first, received_partition.second);
  }

  // Figure out the average number of points across all MPI processes.
  boost::mpi::all_reduce(
    world, static_cast<double>(this->num_points()),
    average_num_points_, std::plus<double>());
  average_num_points_ /= static_cast<double>(world.size());
}

bool MatrixDataset::Init(const std::string &file_name_in) {
  core::data::CSVParser::Parse(
    file_name_in, this, -1, std::numeric_limits<int>::max());
  average_num_points_ = this->num_points();
  return true;
}

void MatrixDataset::Init(
  boost::mpi::communicator &world, int num_attributes, int num_points) {
  this->core::data::Dataset::Init(world, num_attributes, num_points);
}

int MatrixDataset::num_points() const {
  return matrix_.n_cols;
}

void MatrixDataset::RandomGenerate(
  boost::mpi::communicator &world, int num_dimensions, int num_points) {

  // Each process generates its own random data, dumps it to the file,
  // and read its own file back into its own distributed table.
  this->Init(world, num_dimensions + 1, num_points);
  core::util::Random random;
  for(int j = 0; j < num_points; j++) {
    core::data::alias_vec *targets = NULL;
    core::data::alias_vec &point =
      this->get_instance(j, &targets);
    for(int i = 0; i < num_dimensions; i++) {
      point[i] = random.Uniform(0.1, 1.0);
    }
    int random_label = random.Integer(2);
    (*targets)[0] = (random_label == 0) ? -1 : 1;
  }
  std::cerr <<
            "Process " << world.rank() <<
            " generated " << num_points << " points in " <<
            num_dimensions << " dimensionality...\n";
}
}
}
